/**
 * Software License, Version 1.0
 * 
 * Copyright 2003 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgement:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgement may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or NaradaBrokering, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called NaradaBrokering, 
 * nor may Indiana University or Community Grids Lab or NaradaBrokering appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 *LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 *WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 *NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 *INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 *INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 *"VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 *LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 *ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 *GENERATED USING SOFTWARE.
 */
package cgl.narada.protocol;


import cgl.narada.util.ByteUtilities;

/**
   This class maintains the destinations that are associated with any given 
   node in the connectivity graph. The class contains utilities methods for
   updating destination lists and also for the invalidation of specific
   entries in the destination Lists.
   @author Shrideep Pallickara
   $Revision$
   $Date$
*/

public class Destinations implements ProtocolDebugFlags {
  private int systemLevel;
  private int[] destinations;
  private byte[] destinationInBytes;
  private String moduleName = "Destinations: ";
  
  /** Constructs a destination list with the specified systemLevel
      indicator
      @param systemLevel - The level of the system.
  */
  public Destinations(int systemLevel) {
    this.systemLevel = systemLevel; 
    destinations = new int[systemLevel+1];
    for (int i=0; i < destinations.length ; i++) {
      destinations[i] = 0;
    }
    destinationInBytes = new byte[4*(systemLevel+1)];
  }
  
  /** Constructs a destinations object, with the routing information
      received as a sequence of bytes. 
      @param routingInformation - The sequence of bytes that constitutes the
      routing information */
  public Destinations(byte[] routingInformation) {
    systemLevel = routingInformation.length/4 -1;
    destinations = new int[systemLevel+1];
    destinationInBytes = routingInformation;
    transformByteArrayToIntegerArray();
  }
  

  /** Transforms an array of bytes into an array of integers */
  private void transformByteArrayToIntegerArray() {
    byte[] tempVar = new byte[systemLevel + 1];
    int offset =0;
    for (int i=0; i < systemLevel+1; i++) {
      System.arraycopy(destinationInBytes, offset, tempVar, 0, 4);
      destinations[i] = ByteUtilities.getInt(tempVar);
      offset+=4;
    }
  }
  
  /** Java objects are passed by reference. This allows us to update a
      destinations instance with some other. This is specifically  used in
      tandem with the <i>hopsToReachDestination</i> method in the routing
      cache */
  public void updateWithDestination(Destinations _update) {
    int[] updates = _update.getDestinationsInInts();
    for (int i=0; i < systemLevel+1; i++) {
      destinations[i] = destinations[i] | updates[i];
    }
  }
  
  /** This updates the destinations associated with a certain hop. The
      <i>gateway</i> indicates the gateway that can be reached the fastest
      from the hop in question 
      @param gateway The gateway in the connectivity graph which can be
      reached the fastest from the hop in question */
  public void updateDestinationList(Gateway gateway) {
    int updateIndex = systemLevel - gateway.getLevel();
    
    int nodeValue = 
      ByteUtilities.getInt(gateway.getNode().getAddressInBytes());
    destinations[updateIndex] = destinations[updateIndex] | nodeValue;
  }
  

  /** This method eliminates the provided gateway's entry from the set of
      destinations encapsulated within this structure.  
      @param gateway The gateway in the connectivity graph that will be 
      removed.*/
  public void removeFromDestinationList(Gateway gateway) {
    int updateIndex = systemLevel - gateway.getLevel();
    
    int nodeValue = 
      ByteUtilities.getInt(gateway.getNode().getAddressInBytes());
    destinations[updateIndex] = destinations[updateIndex] ^ nodeValue;    
  }


  /** This is added to account for the MatchingProblem scenario */
  public void updateDestinationList(Destinations _destination) {
    int[] updates = _destination.getDestinationsInInts();
    for (int i=0; i < systemLevel+1; i++) {
      destinations[i] = updates[i] | destinations[i];
    }
  }

  /** For a destination list associated with a hop, in response to the
      addition or failure of a <i>gatewayLevel</i> connection, the
      shortest path to nodes <em>=></em> gatewayLevel is updated. The first
      step in the updating procedure is to invalidate the destinations
      associated with a hop for destinations > gatewayLevel. This method
      performs this function
      @param gatewayLevel - The levels that need to be invalidated 
  */
  public void invalidateDestinationsAtLevel(int gatewayLevel) {
    int numOfIterations = systemLevel - gatewayLevel;
    for (int i = gatewayLevel; i< numOfIterations+1; i ++) {
      destinations[systemLevel-i] = 0;
    }
  }

  /** Returns the destinations as a sequence of bytes */
  public byte[] getDestinationsInBytes() {
    int offset=0;
    for (int i=0; i <systemLevel+1; i++) {
      System.arraycopy(ByteUtilities.getBytes(destinations[i]), 0, 
		       destinationInBytes, offset, 4);
      offset+=4;
    }
    return destinationInBytes;
  }

  /** Returns the destinations as a sequence of integers 
      @return The destination as a sequence of integers*/
  public int[] getDestinationsInInts() {
    return destinations;
  }

  /** Returns the systemLevel associated with the destination 
   @return The systemLevel*/
  public int getSystemLevel() {
    return systemLevel;
  }
  
  public String toString() {
     String val = "\n";
     for (int i=0; i<=systemLevel; i++) {
        int level = systemLevel -i;
        val += ("\t" + ByteUtilities.printInt(destinations[i]) + " (" + level + ")\n");
     }
     
     return val;
     
     /**
    getDestinationsInBytes();
    String val = "{";
    for (int i=0; i <destinationInBytes.length/4; i++) {
      val = val + ByteUtilities.printByte(destinationInBytes[4*i]) +  
	ByteUtilities.printByte(destinationInBytes[4*i +1]) + 
	ByteUtilities.printByte(destinationInBytes[4*i +2]) + 
	ByteUtilities.printByte(destinationInBytes[4*i +3]) + "\n" ;
    }
    return val;
    */
  }
  
  /** This performs an inversion of the bits in an integer. There are no
      unary operators which do this in Java. I have implemented the NOT
      operation using an XOR operator */
  private int  performNotOperation(int value) {
    int newVal = value ^ 0xFFFFFFFF;
    return newVal;
  }


  /** This method gets the destinations that need to be reached at a given 
      level.
      @param level The destinations that need to be retrieved for a given 
      level. 
  */
  public int getDestinationsAtLevel(int level) {
    if (level > systemLevel+1) {
      System.out.println(moduleName + "Malformed GET Access ");
    }
    return destinations[systemLevel-level];
  }
  
  /** This method sets the destinations at a given level
      @param level - The level for which the destination needs to be updated
      @param value - The destinations for <i>level</i> encoded within the 
      integer <i>value</i>
  */
  public void setDestinationsAtLevel(int level, int value) {
    if (level > systemLevel+1) {
      System.out.println(moduleName + "Malformed SET Access ");
    }
    int index = systemLevel-level;
    //System.out.println(moduleName + " Set INDEX=" + index + ", VALUE=" + value);
    destinations[index] = value;
  }
  
  /** Given a set of destinations that needs to be reached. This method
      computes the destinations that cannot be reached using the hop that the
      destination is associated with. For a set of hops emanating from the 
      vertex node in the connectivity graph, we pass through the hops 
      sequentially with the result of the invocation of this method on one hop
      being used as an argument to this method in the other hop. The return 
      value indicates several things to us. There are 3 cases which are of 
      importance. 
      <li> <b>returnValue = destinationsToReach</b> - This case implies that 
      none of the destinations could be reached. 
      <li> <b>returnValue == 0</b> -  This case implies that all the 
      destinations can be reached from the hop that this <i>destinations</i> 
      is associated with.
      <li><b>returnValue !=0 && returnValue != destinationsToReach </b>- This 
      case means that some destinations could be reached from the hop. 
      However, not all destinations could be reached 
      @return The destinations that could not reached using a given hop.
  */ 
  public Destinations 
  computeDestinationsThatCannotBeReached(Destinations destinationsToReach) {
    /** This is the destinations that we will return as not being able to 
	reach */
    Destinations cannotReach = new Destinations(systemLevel);
    /* if (destinationsToReach.length != destination.length) {
       System.out.println("Destination: Malformed Reachability request ");
       return null;
       }*/
    int[] toReach = destinationsToReach.getDestinationsInInts();
    int reachability;
    for (int i=0; i < toReach.length; i++) {
      reachability = 
	destinationsThatCannotBeReached(destinations[i], toReach[i]);
      cannotReach.setDestinationsAtLevel(i, reachability);
    } 
    return cannotReach;
  }
  
  /** Returns the destinations that cannot be reached. This is invoked on
      a specific level */
  private int destinationsThatCannotBeReached(int reachability, 
					     int tryToReach) {
    int cannotReach = tryToReach & performNotOperation(reachability); 
    if (Destinations_Debug) {
      System.out.print("Reachability =                     " + 
		       printInteger(reachability) );
      System.out.print("Trying to reach =                  " + 
		       printInteger(tryToReach) );
      System.out.print("Destinations that cannot be reached" +
		       printInteger(cannotReach));
      if (cannotReach == tryToReach) {
	System.out.println("None of the destinations can be reached");
      }
      if (cannotReach ==0) {
	System.out.println("All the destinations can be reached");
      }
      if (cannotReach !=0 && cannotReach!= tryToReach) {
	System.out.println("Some of the destinations could not be reached");
      }
    }
    return cannotReach;
  }
  

  /** A utility method for testing my results ... */
  private String printInteger(int value) {
    String temp="";
    byte[] valueBytes = ByteUtilities.getBytes(value);
    for (int i=0; i<4; i++) {
      temp+=ByteUtilities.printByte(valueBytes[i]);
    } temp+= "\n";
    return temp;
  }
  
  /**
     Compares two objects for equality. Returns a boolean that indicates
     whether this object is equivalent to the specified object. This method
     is used when an object is stored in a hashtable.
     @param obj the Object to compare with
     @return true if these Objects are equal; false otherwise.
     @see java.util.Hashtable
  */
  public boolean equals(Object obj) {
    int[] _destinations = ((Destinations)obj).getDestinationsInInts();
    if (destinations.length != _destinations.length) return false;
    for (int i=0; i < destinations.length; i++) {
      if (destinations[i] != _destinations[i]) {
	return false;
      }
    }
    return true;
  }
  
  /**
     Generates a hash code for the receiver. This method is supported 
     primarily for hash tables, such as those provided in java.util.
     If two objects are equal (equals(Object) returns true) they must have the 
     same hash code
     @return an integer hash code for the receiver 
  */
  public int hashCode() {
    return destinations.hashCode();
  }

  /** When a destination is being sent over a Level <i>level</i> gateway,
      the system is not interested in the destinations that exist at
      lower levels. These destinations would be recomputed at the
      receiving node.
      @param level The gateway level over which the destinations are gonna be
      sent over.
      @return The byte stream of the destinations
  */
  public byte[] prepareDestinationsToSendOverLevel(int level) {
     getDestinationsInBytes();
    byte[] destBytes = new byte[4*(systemLevel+1)];
    System.arraycopy(destinationInBytes,0, 
		     destBytes, 0, 4*(systemLevel+1-level) );
    return destBytes;
  }

  /** Every node has a destination identifier. When a message is received
      over a link, the destinations at which the message was received needs 
      to be updated to reflect the fact that this destination was received 
      at this server node. This method updates a destination list to indicate
      that it was delivered at the server node.
      @param destinationToBeAdded The destination associated with the server
      node.
  */
  public void markAsTraversedAt(Destinations destinationToBeAdded) {
    int[] _destMarker = destinationToBeAdded.getDestinationsInInts();
    for (int i=0; i < (systemLevel+1); i++) {
      destinations[i] = destinations[i] | _destMarker[i];
    }
    
  }

  public static void main(String[] args) {
    Destinations dest = new Destinations(3);
    /*dest.destinationsThatCannotBeReached(93, 93);
      dest.destinationsThatCannotBeReached(45, 16);
      dest.destinationsThatCannotBeReached(45, 93);
      dest.setDestinationsAtLevel(1, 44);
      Destinations dest2 = new Destinations(3);
      dest2.setDestinationsAtLevel(1,44);
      if (!dest2.equals(dest)) {
      System.out.println("problem dude ..");
      }
    */
    int[] a6 = {1}; 
    int[] a4 = {2}; 
    int[] a5 = {3};
    
    Gateway g6 = new Gateway(new NodeAddress(a6), (short)0, true);
    Gateway g4 = new Gateway(g6, new NodeAddress(a4), (short)2);
    Gateway g5 = new Gateway(g6, new NodeAddress(a5), (short)1);

    dest.updateDestinationList(g6);
    dest.updateDestinationList(g4);
    dest.updateDestinationList(g5);
    System.out.println(dest);
    
    dest = new Destinations(dest.prepareDestinationsToSendOverLevel(1));
    System.out.println(dest);

    
  }
}
